```latex
%%  Copyright by Wenliang Du.                                       %%
%%  This work is licensed under the Creative Commons                %%
%%  Attribution-NonCommercial-ShareAlike 4.0 International License. %%
%%  To view a copy of this license, visit                           %%
%%  http://creativecommons.org/licenses/by-nc-sa/4.0/.              %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\commonfolder}{../../common-files}

\input{\commonfolder/header}
\input{\commonfolder/copyright}

\newcommand{\pcap} {\texttt{pcap}\xspace}
\newcommand{\telnet} {\texttt{telnet}\xspace}

\lhead{\bfseries SEED Labs -- Packet Sniffing and Spoofing Lab}


\begin{document}

\begin{center}
{\LARGE Packet Sniffing and Spoofing Lab}
\end{center}

\seedlabcopyright{2006 - 2020}

\newcounter{task}
\setcounter{task}{1}
\newcommand{\tasks} {\bfseries {\noindent (\arabic{task})} \addtocounter{task}{1} \,}

\section{概述}

包嗅探和欺骗是网络安全性中的两个重要概念；它们是网络通信中两大主要威胁。了解这些威胁对于理解网络的安全措施至关重要。有许多包嗅探和欺骗工具，如{\tt Wireshark}, {\tt Tcpdump}, {\tt Netwox}, \texttt{Scapy} 等等。这些工具被安全专家广泛使用，也常被攻击者使用。掌握如何使用这些工具很重要，但更重要的是理解这些工具的原理，即如何在软件中实现包嗅探和欺骗。

本实验的目标是两方面的：学习使用这些工具以及理解这些工具背后的底层技术。对于第二个目标，学生需要编写简单的嗅探器和欺骗程序，并深入了解这些程序的技术细节。本实验涵盖了以下主题：

\begin{itemize}[noitemsep]
    \item 嗅探和欺骗的工作原理
    \item 使用{\tt pcap}库和Scapy进行包嗅探
    \item 使用原始套接字和Scapy进行包欺骗
    \item 使用Scapy操作包
\end{itemize}

\paragraph{阅读资料与视频。}
在以下内容中可以详细涵盖嗅探和欺骗：

\begin{itemize}
    \item SEED书中的第15章，\seedbook
    \item SEED讲座的第2节，\seedisvideo
\end{itemize}

\paragraph{实验环境。} \seedenvironmentC

\paragraph{给讲师的提示。}
本实验包含两组任务。第一组任务侧重于使用工具进行包嗅探和欺骗。这只需要一点点Python编程（通常只需几行代码）；学生不需要有Python编程的基础。

第二组任务主要是为计算机科学/工程专业的学生设计。学生需要从头开始编写C程序来完成嗅探和欺骗。这样，他们可以更深入地了解实际的嗅探器和欺骗工具是如何工作的。学生需要具备扎实的编程基础才能完成这些任务。这两组任务是独立的；讲师可以根据学生的编程背景选择分配一组或两组给学生。

% *******************************************
% SECTION
% ******************************************* 
\section{使用容器设置环境} 

在本实验中，我们将使用三个连接在同一局域网中的机器。我们可以使用三个虚拟机或三个容器来实现这一点。图~\ref{fig:labsetup}展示了使用容器配置的实验室环境。

我们将在攻击者容器上执行所有攻击操作，而其他容器作为用户机器使用。

\begin{figure}[htb]
    \centering
    \includegraphics[width=0.8\textwidth]{\commonfolder/Figs/OneLan_twohosts.pdf}
    \caption{实验室环境配置}
    \label{fig:labsetup}
\end{figure}

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{容器设置与命令} 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/container/setup}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{关于攻击者容器} 

在本实验中，我们可以使用虚拟机或攻击者容器作为攻击机器。如果你查看Docker Compose文件，你会看到攻击者容器的配置与其他容器有所不同。以下是不同之处：

\begin{itemize}
    \item \textit{共享文件夹。} 当我们使用攻击者容器执行攻击时，需要将攻击代码放入攻击者容器中。
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \input{\commonfolder/container/volumes}
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    \item \textit{主机模式。}
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \input{\commonfolder/container/host_mode}
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\end{itemize}

\paragraph{获取网络接口名称。}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/container/interface}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% *******************************************
% SECTION
% ******************************************* 
\section{使用Scapy进行嗅探与欺骗的任务集1}

许多工具都可以用来进行包嗅探和欺骗，但大多数只提供固定的功能。Scapy不同：它不仅可以作为工具使用，还可以作为一个构建块来构建其他嗅探和欺骗工具。即，我们可以将Scapy的功能集成到自己的程序中。在这一组任务中，我们将使用Scapy完成每一项任务。

要使用Scapy，我们可以编写一个Python程序，然后用Python执行这个程序。以下是一个示例。我们需要以根权限运行Python，因为进行包欺骗需要这样的权限。在程序的开头（行~\ding{192}），我们应该导入所有Scapy的模块。

\begin{lstlisting}
#!/usr/bin/env python3

from scapy.all import *    (*@\ding{192}@*)

a = IP()
a.show()

# python3 mycode.py
###[ IP ]###
  version   = 4
  ihl       = None
  ...

// 将mycode.py设置为可执行文件（另一种运行Python程序的方法）
# chmod a+x mycode.py
# mycode.py 
\end{lstlisting}

我们也可以进入Python的交互模式，然后在Python提示符下逐行运行我们的代码。如果需要频繁修改代码进行实验，这种方法更方便。

\begin{lstlisting}
# python3
>>> from scapy.all import *
>>> a = IP()
>>> a.show()
###[ IP ]###
  version   = 4
  ihl       = None
  ...
\end{lstlisting}

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务1.1：嗅探包}  

Wireshark是最流行的嗅探工具，使用也很方便。在整个实验中我们将继续使用它。然而，作为构建其他工具的组件时很难使用Wireshark。我们会用Scapy来完成这个目的。本任务的目标是学习如何在Python程序中使用Scapy进行包嗅探。提供了一个样本代码如下：

\begin{lstlisting}
#!/usr/bin/env python3
from scapy.all import *

def print_pkt(pkt):
  pkt.show()

pkt = sniff(iface='br-c93733e9f913', filter='icmp', prn=print_pkt)
\end{lstlisting}

上述代码将在\texttt{br-c93733e9f913}接口上嗅探包。请查阅实验设置部分的说明以获取正确的接口名称。如果我们想在多个接口上进行嗅探，可以将这些接口放入一个列表中，并将其分配给\texttt{iface}。
例如：

\begin{lstlisting}
iface=['br-c93733e9f913', 'enp0s3']
\end{lstlisting}

\paragraph{任务1.1A。} 在上面的程序中，对于每个捕获到的包，回调函数\texttt{print_pkt()} 将被调用；这个函数将打印出一些关于该包的信息。请以root权限运行程序并演示你确实可以捕获包。然后，再次运行程序但不使用 root 权限；描述和解释你的观察结果。

\begin{lstlisting}
// 使程序可执行
# chmod a+x sniffer.py

// 使用root权限运行该程序
# sniffer.py

// 切换到 "seed" 账户，然后以非root权限重新运行程序
# su seed
$ sniffer.py
\end{lstlisting}

\paragraph{任务1.1B。} 通常，在嗅探包时我们只对某些类型的包感兴趣。可以通过在嗅探器中设置过滤器来实现这一点。Scapy的过滤器使用 BPF（Berkeley Packet Filter）语法；你可以从网络上找到BPF的手册。请设置以下过滤器并再次运行你的嗅探程序（每个过滤器应单独设置一次）：

\begin{itemize}
    \item 仅捕获ICMP包
    \item 捕获来自特定IP且目的端口号为23的任何TCP包。
    \item 捕获发往或来自于特定子网的包。你可以选择任意一个子网，如\texttt{128.230.0.0/16}；你应该不要选择你的虚拟机所连接的那个子网。
\end{itemize}

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务1.2：欺骗ICMP包}

作为包欺骗工具，Scapy允许我们将IP包的字段设置为任意值。本任务的目标是用任意源IP地址欺骗IP包，并将其发送到同一网络中的另一个虚拟机。我们将在Wireshark中观察接收方是否接受了我们的请求。如果接受，则会向欺骗的IP地址发送一个回显回复包。

以下代码展示了一个如何欺骗ICMP包的例子：

\begin{lstlisting}
>>> from scapy.all import *
>>> a = IP()              (*@\ding{192}@*)
>>> a.dst = '10.0.2.3'    (*@\ding{193}@*)
>>> b = ICMP()            (*@\ding{194}@*)
>>> p = a/b               (*@\ding{195}@*)
>>> send(p)               (*@\ding{196}@*)
.
Sent 1 packets.
\end{lstlisting}

在上述代码中，第~\ding{192}行创建了一个从IP类生成的IP对象；对于每个IP头部字段都有一个类属性。我们可以使用 \texttt{ls(a)} 或 \texttt{ls(IP)} 查看所有属性名称/值。我们也可以使用 a.show() 和 IP.show() 做同样的事情。第~\ding{193}行展示了如何设置目的IP地址字段。如果未设置某个字段，则将使用默认值。

\begin{lstlisting}
>>> ls(a)
version    : BitField (4 bits)       = 4               (4)
ihl        : BitField (4 bits)       = None            (None)
tos        : XByteField              = 0               (0)
len        : ShortField              = None            (None)
id         : ShortField              = 1               (1)
flags      : FlagsField (3 bits)     = <Flag 0 ()>     (<Flag 0 ()>)
frag       : BitField (13 bits)      = 0               (0)
ttl        : ByteField               = 64              (64)
proto      : ByteEnumField           = 0               (0)
chksum     : XShortField             = None            (None)
src        : SourceIPField           = '127.0.0.1'     (None)
dst        : DestIPField             = '127.0.0.1'     (None)
options    : PacketListField         = []              ([])
\end{lstlisting}

第~\ding{194}行创建了一个ICMP对象。默认类型为回显请求。在第~\ding{195}行中，我们将 \texttt{a} 和 \texttt{b} 放在一起形成一个新对象。\texttt{/} 运算符被 IP 类重载，不再表示除法；而是将 \texttt{b} 作为 \texttt{a} 的负载字段添加并相应修改了 \texttt{a} 的字段。因此我们得到一个新的代表ICMP包的对象。我们现在可以使用第~\ding{196}行中的 send() 发送此包。请对样本代码进行必要的更改，然后演示你如何用任意源IP地址欺骗一个回显请求包。

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务1.3：Traceroute}

本任务的目标是使用Scapy估算你的虚拟机与选定目标之间的距离（以路由器数量为单位）。这是 \texttt{traceroute} 工具实现的基本功能。在本任务中，我们需要编写自己的工具。想法很简单：向目的地发送一个包（任何类型），将它的生存时间（TTL）字段设置为1。该包会被第一个路由器丢弃，并返回一个ICMP错误消息，告诉我们其生存时间已超时。这就是我们获取第一个路由器IP地址的方式。然后我们将TTL字段增加到2，发送另一个包并获取第二个路由器的IP地址。我们会重复这个过程直到最终到达目的地。需要注意的是，这次实验只能获得估算的结果，因为在理论上，并非所有这些包都会沿相同的路径传输（但在实践中，在较短的时间段内可能如此）。以下代码展示了这一过程中的一个循环。

\begin{lstlisting}
a = IP()
a.dst = '1.2.3.4'
a.ttl = 3
b = ICMP()
send(a/b)
\end{lstlisting}

如果你是经验丰富的Python程序员，你可以编写工具以自动执行整个过程。如果你刚开始接触Python编程，可以通过手动更改每轮的TTL字段，并根据Wireshark观察结果记录IP地址来完成任务。任何一种方法都可接受，只要你能获得正确的结果。

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务1.4：嗅探和-然后欺骗}  

在这个任务中，你需要结合使用嗅探技术和欺骗技术实现以下嗅探与欺骗程序。你需要在同一个局域网中的两台机器上运行该程序：虚拟机和用户容器。从用户容器出发，你发送一个ping命令给IP X。这将生成一个ICMP回显请求包。如果X存在，则ping程序会收到一个回显回复，并打印出响应。你的嗅探与欺骗程序在虚拟机上运行，通过嗅探来监控局域网。每当看到一个ICMP回显请求时（无论目标IP地址是什么），你的程序应立即使用包欺骗技术发送一个回显回复。因此，不论机器X是否存在，ping程序都会始终收到一个回显回复，表明X是存在的。你需要使用Scapy完成这个任务。在你的报告中，你需提供证据以证明你的技术有效。

在实验过程中，你应该从用户容器对以下三个IP地址执行 ping 命令。
报告观察结果并解释结果。

\begin{lstlisting}
ping 1.2.3.4     # 互联网上的一个不存在的主机
ping 10.9.0.99   # 局域网上的一个不存在的主机
ping 8.8.8.8     # 互联网上存在的一个主机
\end{lstlisting}

\paragraph{提示：}你需要理解 ARP 协议的工作原理，以便正确解释你的观察结果。你还需要知道一些关于路由的基本知识。以下命令有助于查找特定目的地的路由器：

\begin{lstlisting}
ip route get 1.2.3.4 
\end{lstlisting}

% *******************************************
% SECTION
% ******************************************* 
\section{编写程序进行嗅探和欺骗的任务集2}

对于此任务集，你应该在主机虚拟机中编译C代码，并将代码运行到容器中。你可以使用 \texttt{"docker cp"} 命令从主机虚拟机复制文件到容器内。
例如（不需要完整输入docker ID）：

\begin{lstlisting}
$ dockps
f4501a488a69  hostA-10.9.0.5
85058cbdee62  hostB-10.9.0.6
24cbc879e371  seed-attacker

// 将 a.out 复制到 seed-attacker 容器的 /tmp 文件夹中
$ docker cp a.out 24cbc879e371:/tmp
\end{lstlisting}

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务2.1：编写包嗅探程序}

可以使用 \pcap 库轻松编写嗅探器程序。借助 \pcap，嗅探器的任务只需调用 \pcap 库中的一系列简单过程即可。序列结束后，捕获到的包将被放入缓冲区以备进一步处理。所有的细节都由 \pcap 库来处理。SEED 书籍提供了一个示例代码，展示了如何使用 \pcap 编写一个简单的嗅探器程序。我们附上该示例代码（参见书籍中详细的解释）。

\begin{lstlisting}
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>

/* 当捕获到包时这个函数会被 pcap 调用。我们在函数内部可以处理每个包。 */
void got_packet(u_char *args, const struct pcap_pkthdr *header,
        const u_char *packet)
{
   printf("Got a packet\n");
}

int main()
{
  pcap_t *handle;
  char errbuf[PCAP_ERRBUF_SIZE];
  struct bpf_program fp;
  char filter_exp[] = "icmp";
  bpf_u_int32 net;

  // 步骤1：在名为 eth3 的网络接口上打开实时的 pcap 会话。学生需要将 "eth3" 更改为自己的机器上的名称（使用 ifconfig 查找）。如果使用容器设置，则具有前缀 "br-" (例如，if the container setup is used)。
  handle = pcap_open_live("eth3", BUFSIZ, 1, 1000, errbuf); 

  // 步骤2：将 filter_exp 编译为 BPF 模拟代码
  pcap_compile(handle, &fp, filter_exp, 0, net);            
  if (pcap_setfilter(handle, &fp) !=0) {                   
      pcap_perror(handle, "Error:");
      exit(EXIT_FAILURE);
  }

  // 步骤3：捕获包
  pcap_loop(handle, -1, got_packet, NULL);                  

  pcap_close(handle);   //关闭会话句柄
  return 0;
}

// 注意：不要忘记将 "-lpcap" 添加到编译命令中。例如: gcc -o sniff sniff.c -lpcap
\end{lstlisting}

Tim Carstens 还写了一篇关于如何使用 \pcap 库编写嗅探器程序的教程，该教程可以在 \url{http://www.tcpdump.org/pcap.htm} 查看。

\paragraph{任务2.1A：理解嗅探器的工作原理}
在这个任务中，学生需要编写一个嗅探器程序来打印每个捕获到包的源和目的IP地址。学生可以输入上述代码或从 SEED 书籍网站（\url{https://www.handsonsecurity.net/figurecode.html}）下载样本代码。学生应提供截图作为证据以证明其嗅探器程序能够成功运行并产生预期结果。此外，请回答以下问题：

\begin{itemize}
    \item \textbf{问题1。} 请用自己的话描述对于嗅探器程序必不可少的库调用序列。这是旨在进行总结，而不是像教程或书中那样详细解释。
    
    \item \textbf{问题2。} 为什么需要root权限来运行嗅探器程序？如果未使用root权限执行该程序，程序会在哪里失败？
    
    \item \textbf{问题3。} 请在你的嗅探器程序中打开和关闭混杂模式。\texttt{pcap\_open\_live()} 的第三个参数值为1时开启混杂模式（用0来关闭）。你能否演示当此模式开启与关闭时的区别？请描述如何进行演示。你可以使用以下命令检查一个接口的混杂模式是否开启或关闭（注意 \texttt{promiscuity} 的值）。

\begin{lstlisting}
# ip -d link show dev br-f2478ef59744
1249: br-f2478ef59744: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
    link/ether 02:42:ac:99:d1:88 brd ff:ff:ff:ff:ff:ff (*@\textbf{promiscuity 1}@*) ...
\end{lstlisting}
\end{itemize}

\paragraph{任务2.1B：编写过滤器。}请为你的嗅探器程序编写过滤表达式以捕获以下各项。
你可以在网上找到 \pcap 过滤的手册。
在你的实验报告中，你需要包含应用每个这些过滤器后的截图。

\begin{itemize}
    \item 捕获两个特定主机之间的ICMP包。
    \item 捕获目的端口号范围从10到100的TCP包。  
\end{itemize}

\paragraph{任务2.1C：嗅探密码。}请展示如何使用你的嗅探器程序捕获用户在监控网络上使用 \telnet 时输入的密码。
你可能需要修改你的嗅探代码以打印出捕获到的TCP包的数据部分（\telnet 使用TCP）。如果打印整个数据部分，然后手动标注出密码的位置是可以接受的。

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务2.2：欺骗}

当普通用户发送一个包时，操作系统通常不允许用户设置协议头中的所有字段（如TCP、UDP和IP头部）。大多数字段由操作系统设置，只有少数字段允许用户设置，例如目的IP地址、目的地端口号等。但是，如果用户具有root权限，则可以设置任何任意字段。这是称为包欺骗的现象，并可以通过 {\em raw sockets} 实现。

Raw sockets 让程序员绝对控制包的构建过程，可以让程序员构建任意的包，包括设置头部字段和负载部分。
使用raw sockets相对简单；它涉及四个步骤：(1) 创建一个raw socket，(2) 设置套接字选项，(3) 构建包，(4) 通过 raw socket 发送包。有许多在线教程可以教你如何在C编程中使用raw sockets。我们已将一些教程链接到了实验的网页上，请阅读它们，并学习如何编写包欺骗程序。
这里展示了一个简单的骨架代码。

\begin{lstlisting}
int sd;
struct sockaddr_in sin;
char buffer[1024]; // 你可以更改缓冲区大小

/* 创建一个带有IP协议的 raw socket。IPPROTO_RAW 参数告诉系统 IP 头部已经被包含了；这可以防止操作系统添加另一个 IP 头部。 */
sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if(sd < 0) {
    perror("socket() error"); exit(-1);
}

/* 这个数据结构在使用套接字发送包时需要。通常我们需要填充多个字段，但对 raw sockets 而言，我们只需要填充这个一个字段 */
sin.sin_family = AF_INET;

// 在这里你可以用 buffer[] 构造 IP 包
//   - 构建 IP 头部 ...
//   - 构建 TCP/UDP/ICMP 头部 ...
//   - 如果需要的话填充数据部分 ...
// 注意：你应该注意网络/主机字节顺序。

/* 发送 IP 包。
 * ip_len 是实际包大小。 */  
if(sendto(sd, buffer, ip_len, 0, (struct sockaddr *)&sin, 
              sizeof(sin)) < 0) {
      perror("sendto() error"); exit(-1);
}
\end{lstlisting}

\paragraph{任务2.2A：编写一个欺骗程序。}请用C编写自己的包欺骗程序。你需要提供证据（例如，Wireshark 包跟踪）来证明你的程序成功发送了欺骗的IP包。

\paragraph{任务2.2B：欺骗ICMP回显请求。}
代表另一个机器（即，使用其 IP 地址作为源 IP 地址）发送一个 ICMP 回显请求包。这个包应该发往互联网上的另一台机器（该机器必须存在）。你应该启动你的 Wireshark，如果欺骗成功，你可以在远程机器返回的回显回复中看到它。

\paragraph{问题：}请回答以下问题：

\begin{itemize}
    \item \textbf{问题4。} 你可以将IP包长度字段设置为任意值吗，而与实际包大小无关？
    
    \item \textbf{问题5。} 使用 raw sockets 编程时你必须计算 IP 头部的校验和吗？

    \item \textbf{问题6。} 为什么需要root权限来运行使用raw sockets的程序？如果未执行root权限，程序会在哪里失败？
\end{itemize}

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务2.3：嗅探和然后欺骗}

在这个任务中，你需要结合使用嗅探技术和欺骗技术实现以下嗅探与欺骗程序。你将需要局域网中的两台机器。从机器 A 向 IP X 发送 ping 命令。这将生成一个ICMP回显请求包。如果X存在，ping程序会接收到一个回显回复，并打印出响应。你的嗅探与欺骗程序将在攻击者机器上运行，通过嗅探来监控局域网。每当看到一个ICMP回显请求时（无论目标IP地址是什么），你的程序应立即使用包欺骗技术发送一个回显回复。
因此，不论机器X是否存在，ping程序总是会收到一个表示X存在的回显回复。你需要用C语言编写这样的程序，并在报告中附上截图以显示你的程序有效运行。请一并附上代码（带有适当注释）。

% *******************************************
% SECTION
% ******************************************* 
\section{指导方针} 

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{在Raw包中填充数据}

当你使用raw sockets发送一个包时，你基本上是在缓冲区中构建该包，所以当需要发送它时，你可以简单地将操作系统的缓冲区和包大小告知操作系统。直接在缓冲区内工作不那么容易，因此一种常见的方式是将缓冲区（或缓冲区的一部分）类型转换为结构体，如IP头部结构体，这样就可以通过这些结构体的字段来引用缓冲区内的元素。你可以在程序中定义 IP、ICMP、TCP、UDP 和其他头结构。

以下示例展示了如何构造一个 UDP 包：

\begin{lstlisting}
struct ipheader {
   type  field;
   .....
}

struct udpheader {
   type field;
   ......
}

// 这个缓冲区将用于构建原始包。
char buffer[1024];

// 将缓冲区类型转换为 IP 头部结构体
struct ipheader *ip = (struct ipheader *) buffer;

// 将缓冲区类型转换为 UDP 头部结构体
struct udpheader *udp = (struct udpheader *) (buffer
                             + sizeof(struct ipheader));

// 为 IP 和 UDP 头部字段赋值。
ip->field = ...;
udp->field = ...;
\end{lstlisting}

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{注意网络/主机字节顺序和转换}

你需要关注网络和主机字节顺序。如果你使用 x86 CPU，你的主机字节顺序为 {\em Little Endian}，而网络字节序为 {\em Big Endian}。无论你将什么数据放入包缓冲区中都必须使用网络字节序；如果不这样做，你的包将是不正确的。实际上你不需要担心机器使用的具体是哪种 Endian，并且实际上也不应为此而担忧。

你需要做的是在将数据放置到缓冲区时始终记住将其转换为网络字节序，在从缓冲区复制数据到计算机上的一个数据结构之前将其转换为主机字节序。如果数据是一个单个字节，你不需要担心顺序问题，但如果是 \tt short, int, long 或者由多个字节构成的数据类型，你需要调用以下函数之一来转换数据：

\begin{lstlisting}
htonl(): 将 unsigned int 从主机到网络字节序进行转换。
ntohl(): 反向于 htonl()。
htons(): 将 unsigned short int 从主机到网络字节序进行转换。
ntohs(): 反向于 htons()。
\end{lstlisting}

你可能还需要使用 {\tt inet\_addr()}, {\tt inet\_network()},
{\tt inet\_ntoa()}, {\tt inet\_aton()} 将 IP 地址（字符串形式的点分十进制）转换为网络/主机字节序中的 32 位整数。你可以从互联网上获取它们的手册。

% *******************************************
% SECTION
% ******************************************* 
\section{提交指南}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/submission}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\end{document}
```